﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {


    /// <summary>
    /// 頂点バッファー
    /// </summary>
    /// <remarks>
    /// 頂点バッファーは複数の頂点データ(<see cref="VertexArray"/>)を保持するコンテナクラスです。
    /// 原則としてエンジン側では頂点データの意味(セマンティクス)は気にしません。
    /// ただし座標位置(Positions)と法線(Normals)の２つだけはエンジン側でも使用するため
    /// ユーザーが教えてやる必要があります。
    /// (メモ)UniformArrayに併せてAddFeedback()ではなくAdd()にすべきか？
    /// </remarks>
    public class FeedbackBuffer : Object {

        #region Field
        List<FeedbackVariable> feedbacks;   // ぱっと見でVertexArrayを持っていることがわかりにくい。変更すべきか？
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public FeedbackBuffer () {
            this.feedbacks = new List<FeedbackVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// 現在セットされている頂点データの総数
        /// </summary>
        public int FeedbackVaryingCount {
            get { return feedbacks.Count; }
        }


        /// <summary>
        /// Feedback変数を列挙する列挙子
        /// </summary>
        public IEnumerable<FeedbackVariable> FeedbackVaryings {
            get { return feedbacks; }
        }

        /// <summary>
        /// VertexArrayを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// nullが含まれる事はありません。
        /// </remarks>
        public IEnumerable<VertexArray> VertexArrays {
            get {
                return from x in feedbacks
                       where x.VertexArray != null
                       select x.VertexArray;
            }
        }

        /// <summary>
        /// このフィードバックバッファーで使用する頂点の数
        /// </summary>
        /// <remarks>
        /// このフィードバックバッファーで使用する頂点アレイの頂点数はすべて同一である必要があります。
        /// まだ頂点アレイが登録されていない場合0が返ります。
        /// </remarks>
        public int VertexCount {
            get {
                if (feedbacks.Count > 0) {
                    return feedbacks[0].VertexArray.VertexCount;
                }
                return 0;
            }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。フィードバック変数のコピー先のVertexArrayは参照に含まれません。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (this.feedbacks.Select (x => x.VertexArray));
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }

        #endregion

        #region Method

        /// <summary>
        /// Feedbackバッファーに頂点データをセットします
        /// </summary>
        /// <remarks>
        /// 名前(<paramref name="name"/>)と頂点データ(<see cref="VertexArray"/>)を指定してAttibute変数を追加します。
        /// 既に登録済みの名前の場合はこれを新しい物に置き換えます。
        /// </remarks>
        /// <param name="name">Feedback変数の名前</param>
        /// <param name="varry">頂点データ</param>
        public void AddVertexArray (string name, VertexArray varry) {
            AddVertexArray (name, varry, null);
        }

        /// <summary>
        /// フィードバックバッファーに出力先となる頂点データをセットします
        /// </summary>
        /// <remarks>
        /// (メモ)このCopyToは廃止すべきか？ 機能としては必要だがどうもUIが気に入らない
        /// </remarks>
        /// <param name="name">Feedback変数の名前</param>
        /// <param name="varry">頂点データ</param>
        /// <param name="copyTo">コピー先のバッファー</param>
        public void AddVertexArray (string name, VertexArray varry, VertexArray copyTo) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            if (VertexCount > 0 && VertexCount != varry.VertexCount) {
                throw new ArgumentException ("All VertexCount must be equal in the same FeedbackBuffer");
            }
            // 同一のVertexArrayを異なる名前で登録するのもエラーにすべきだよね？

            this.feedbacks.RemoveAll (x => x.Name == name);
            this.feedbacks.Add (new FeedbackVariable (name, varry, copyTo));
        }

        public VertexArray GetVertexArray (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return (from x in feedbacks
                    where x.Name == name
                    select x.VertexArray).First ();
        }

        /// <summary>
        /// 頂点データを削除します
        /// </summary>
        /// <remarks>
        /// 指定の頂点データを削除します。そのような頂点データが登録されていなかった場合エラーにはせずに単に無視します。
        /// </remarks>
        /// <param name="name">頂点データ</param>
        /// <returns>削除した要素の数</returns>
        public bool RemoveVertexArray (string name) {
            if (name == null) {
                throw new ArgumentNullException ("Name is null");
            }
            var num = this.feedbacks.RemoveAll (x => x.Name == name);
            return (num > 0) ? true : false;
        }

        /// <summary>
        /// 指定の名前の<see cref="FeedbackVariable"/>変数を取得するインデクサー
        /// </summary>
        /// <param name="name">Attbibute変数名</param>
        /// <returns></returns>
        public FeedbackVariable this[string name] {
            get {
                return feedbacks.Where (x => x.Name == name).FirstOrDefault ();
            }
        }

        /// <summary>
        /// 指定の名前のFeedback変数を取得します
        /// </summary>
        /// <remarks>
        /// 該当するFeedback変数がない場合はデフォルト値が返ります。
        /// </remarks>
        /// <param name="name">Feedback変数名</param>
        /// <returns>Feedback変数</returns>
        public FeedbackVariable GetFeedbackVarying (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return (from x in feedbacks
                    where x.Name == name
                    select x).FirstOrDefault ();
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("VertexBuffer: Feedbacks={0}", FeedbackVaryingCount);
        }
        #endregion


    }
}
